﻿using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using TiaoTiaoCode.Common.Dtos;

namespace TiaoTiaoCode.Common.Extensions
{
    /// <summary>
    /// 字符串类扩展
    /// </summary>
    public static class StringEx
    {
        #region 基本处理

        /// <summary>
        /// 字符串是否为空
        /// </summary>
        /// <param name="s">字符串</param>
        /// <returns></returns>
        public static bool TiaoTiaoIsNullOrEmpty(this string? s)
        {
            if (string.IsNullOrWhiteSpace(s)) return true;

            return false;
        }

        /// <summary>
        /// 字符串是否不空
        /// </summary>
        /// <param name="s">字符串</param>
        /// <returns></returns>
        public static bool TiaoTiaoIsNotNullOrEmpty(this string? s)
        {
            if (string.IsNullOrWhiteSpace(s)) return false;

            return true;
        }

        /// <summary>
        /// 字符串拼接
        /// </summary>
        /// <param name="arr">字符串集合</param>
        /// <param name="separator">连接符</param>
        /// <returns></returns>
        public static string TiaoTiaoJoin(this IEnumerable<string> arr, string separator)
        {
            return string.Join(separator, arr);
        }

        /// <summary>
        /// 字符串是否为数字，是则返回
        /// </summary>
        /// <param name="s"></param>
        /// <param name="result"></param>
        /// <returns></returns>
        public static bool TiaoTiaoIsNumeric(this string s, out double result)
        {
            Regex rex = new Regex(@"^[-]?\d+[.]?\d*$");
            result = -1;
            if (rex.IsMatch(s))
            {
                result = double.Parse(s);
                return true;
            }
            else
                return false;
        }

        /// <summary>
        /// 字符串是否为数字，是则返回
        /// </summary>
        /// <param name="s"></param>
        /// <param name="result"></param>
        /// <returns></returns>
        public static bool TiaoTiaoIsNumeric(this string s, out int result)
        {
            Regex rex = new Regex(@"^[-]?\d+[.]?\d*$");
            result = -1;
            if (rex.IsMatch(s))
            {
                result = int.Parse(s);
                return true;
            }
            else
                return false;
        }

        /// <summary>
        /// 字符串是否为数字，是则返回
        /// </summary>
        /// <param name="s"></param>
        /// <param name="result"></param>
        /// <returns></returns>
        public static bool TiaoTiaoIsNumeric(this string s, out long result)
        {
            Regex rex = new Regex(@"^[-]?\d+[.]?\d*$");
            result = -1;
            if (rex.IsMatch(s))
            {
                result = long.Parse(s);
                return true;
            }
            else
                return false;
        }

        /// <summary>
        /// TrimStart扩展，
        /// 以b的长度作为trimstart的另一个条件，防过多的被替换掉
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        public static string TiaoTiaoTrimStart(this string a, string b)
        {
            var charA = a.ToCharArray();
            var charB = b.ToCharArray();

            if (!a.StartsWith(b)) return a;

            var tmp = a;
            for (var i = 0; i < a.Length; i++)
            {
                if (i >= b.Length) break;
                if (charA[i] != charB[i]) break;
                tmp = tmp.Substring(1);
            }

            return tmp;
        }

        #endregion

        #region Json

        /// <summary>
        /// 转Json
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="obj"></param>
        /// <returns></returns>
        public static string TiaoTiaoToJson<T>(this T obj)
        {
            if (obj == null) return string.Empty;

            return JsonConvert.SerializeObject(obj
                        , Formatting.Indented,
                        new JsonSerializerSettings
                        {
                            ContractResolver = new CamelCasePropertyNamesContractResolver(),
                            DateFormatString = "yyyy-MM-dd HH:mm:ss"
                        });
        }

        /// <summary>
        /// 字符串转Json
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="json"></param>
        /// <returns></returns>
        public static T TiaoTiaoDeserialize<T>(this string json)
        {
            if (json.TiaoTiaoIsNullOrEmpty()) return default!;

            return JsonConvert.DeserializeObject<T>(json)!;
        }

        #endregion

        #region 字符串转其他类型

        /// <summary>
        /// 字符串转Double
        /// </summary>
        /// <param name="s"></param>
        /// <param name="def"></param>
        /// <returns></returns>
        public static double TiaoTiaoToDouble(this string s, double def = 0.0)
        {
            double result;
            return double.TryParse(s, out result) ? result : def;
        }

        /// <summary>
        /// 字符串转Float
        /// </summary>
        /// <param name="s"></param>
        /// <param name="def"></param>
        /// <returns></returns>
        public static float TiaoTiaoToFloat(this string s, float def = 0f)
        {
            float result;
            return float.TryParse(s, out result) ? result : def;
        }

        /// <summary>
        /// 字符串转Byte
        /// </summary>
        /// <param name="s"></param>
        /// <param name="def"></param>
        /// <returns></returns>
        public static byte TiaoTiaoToByte(this string s, byte def = 0)
        {
            byte result;
            return byte.TryParse(s, out result) ? result : def;
        }

        /// <summary>
        /// 字符串转SByte
        /// </summary>
        /// <param name="s"></param>
        /// <param name="def"></param>
        /// <returns></returns>
        public static sbyte TiaoTiaoToSByte(this string s, sbyte def = 0)
        {
            sbyte result;
            return sbyte.TryParse(s, out result) ? result : def;
        }

        /// <summary>
        /// 单字符串转Char
        /// </summary>
        /// <param name="s"></param>
        /// <param name="def"></param>
        /// <returns></returns>
        public static char TiaoTiaoToChar(this string s, char def = '\0')
        {
            char result;
            return char.TryParse(s, out result) ? result : def;
        }

        /// <summary>
        /// 字符串转Decimal
        /// </summary>
        /// <param name="s"></param>
        /// <param name="def"></param>
        /// <returns></returns>
        public static decimal TiaoTiaoToDecimal(this string s, decimal def = 0m)
        {
            decimal result;
            return decimal.TryParse(s, out result) ? result : def;
        }

        /// <summary>
        /// 字符串转Short
        /// </summary>
        /// <param name="s"></param>
        /// <param name="def"></param>
        /// <returns></returns>
        public static short TiaoTiaoToShort(this string s, short def = 0)
        {
            short result;
            return short.TryParse(s, out result) ? result : def;
        }

        /// <summary>
        /// 字符串转UShort
        /// </summary>
        /// <param name="s"></param>
        /// <param name="def"></param>
        /// <returns></returns>
        public static ushort TiaoTiaoToUShort(this string s, ushort def = 0)
        {
            ushort result;
            return ushort.TryParse(s, out result) ? result : def;
        }

        /// <summary>
        /// 字符串转Int
        /// </summary>
        /// <param name="s"></param>
        /// <param name="def"></param>
        /// <returns></returns>
        public static int TiaoTiaoToInt(this string s, int def = 0)
        {
            int result;
            return int.TryParse(s, out result) ? result : def;
        }

        /// <summary>
        /// 字符串转UInt
        /// </summary>
        /// <param name="s"></param>
        /// <param name="def"></param>
        /// <returns></returns>
        public static uint TiaoTiaoToUInt(this string s, uint def = 0u)
        {
            uint result;
            return uint.TryParse(s, out result) ? result : def;
        }

        /// <summary>
        /// 字符串转Long
        /// </summary>
        /// <param name="s"></param>
        /// <param name="def"></param>
        /// <returns></returns>
        public static long TiaoTiaoToLong(this string s, long def = 0L)
        {
            long result;
            return long.TryParse(s, out result) ? result : def;
        }

        /// <summary>
        /// 字符串转ULong
        /// </summary>
        /// <param name="s"></param>
        /// <param name="def"></param>
        /// <returns></returns>
        public static ulong TiaoTiaoToULong(this string s, ulong def = 0uL)
        {
            ulong result;
            return ulong.TryParse(s, out result) ? result : def;
        }

        /// <summary>
        /// 字符串转DateTimne
        /// </summary>
        /// <param name="s"></param>
        /// <param name="def"></param>
        /// <returns></returns>
        public static DateTime TiaoTiaoToDateTime(this string s, DateTime def = default(DateTime))
        {
            DateTime result;
            return DateTime.TryParse(s, out result) ? result : def;
        }

        /// <summary>
        /// 字符串转DateTime
        /// </summary>
        /// <param name="s"></param>
        /// <param name="formatString"></param>
        /// <returns></returns>
        public static DateTime TiaoTiaoToDateTime(this string s, string formatString)
        {
            return DateTime.ParseExact(s, formatString, CultureInfo.InvariantCulture);
        }

        /// <summary>
        /// 字符串转格式化DateTime
        /// </summary>
        /// <param name="s"></param>
        /// <param name="formatString"></param>
        /// <param name="def"></param>
        /// <returns></returns>
        public static DateTime TiaoTiaoToDateTimeEx(this string s, string formatString, DateTime def = default(DateTime))
        {
            int year = def.Year;
            int month = def.Month;
            int day = def.Day;
            int hour = def.Hour;
            int minute = def.Minute;
            int second = def.Second;
            int millisecond = def.Millisecond;
            if (formatString.Contains("yyyy"))
            {
                year = s.Substring(formatString.IndexOf("yyyy", StringComparison.CurrentCulture), 4).TiaoTiaoToInt();
            }
            else if (formatString.Contains("yy"))
            {
                year = 2000 + s.Substring(formatString.IndexOf("yy", StringComparison.CurrentCulture), 2).TiaoTiaoToInt();
            }

            if (formatString.Contains("MM"))
            {
                month = s.Substring(formatString.IndexOf("MM", StringComparison.CurrentCulture), 2).TiaoTiaoToInt();
            }

            if (formatString.Contains("dd"))
            {
                day = s.Substring(formatString.IndexOf("dd", StringComparison.CurrentCulture), 2).TiaoTiaoToInt();
            }

            if (formatString.Contains("HH"))
            {
                hour = s.Substring(formatString.IndexOf("HH", StringComparison.CurrentCulture), 2).TiaoTiaoToInt();
            }

            if (formatString.Contains("mm"))
            {
                minute = s.Substring(formatString.IndexOf("mm", StringComparison.CurrentCulture), 2).TiaoTiaoToInt();
            }

            if (formatString.Contains("ss"))
            {
                second = s.Substring(formatString.IndexOf("ss", StringComparison.CurrentCulture), 2).TiaoTiaoToInt();
            }

            if (formatString.Contains("fff"))
            {
                millisecond = s.Substring(formatString.IndexOf("fff", StringComparison.CurrentCulture), 3).TiaoTiaoToInt();
            }

            try
            {
                return new DateTime(year, month, day, hour, minute, second, millisecond);
            }
            catch
            {
                return def;
            }
        }

        /// <summary>
        /// 转范围
        /// [下限，上限]
        /// </summary>
        /// <param name="s"></param>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        public static (decimal?, decimal?) TiaoTiaoToRange(this string s)
        {
            if (s.TiaoTiaoIsNullOrEmpty()) throw new Exception("范围值为空");

            var regex = new Regex(@"\[(?<low>\d+),(?<high>\d+)\]");
            if (!regex.IsMatch(s)) throw new Exception("不合法的范围值");

            var m = regex.Match(s);
            if (m.Success)
            {
                var gs = m.Groups;

                var low = gs["low"].Value.TiaoTiaoIsNullOrEmpty() ? null : (decimal?)gs["low"].TiaoTiaoSafeTo((decimal)0);
                var high = gs["high"].Value.TiaoTiaoIsNullOrEmpty() ? null : (decimal?)gs["high"].TiaoTiaoSafeTo((decimal)0);

                return (low, high);
            }

            throw new Exception("不匹配的范围值");
        }

        #endregion

        /// <summary>
        /// 判断是否为Url路径
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        public static bool TiaoTiaoIsUrl(this string path)
        {
            return Regex.IsMatch(path, @"((http|ftp|https)://)(([a-zA-Z0-9\._-]+\.[a-zA-Z]{2,6})|([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}))(:[0-9]{1,4})*(/[a-zA-Z0-9\&%_\./-~-]*)?");
        }

        /// <summary>
        /// 字符串转驼峰
        /// </summary>
        /// <param name="s"></param>
        /// <returns></returns>
        public static string TiaoTiaoToCamel(this string s)
        {
            return Thread.CurrentThread.CurrentCulture.TextInfo.ToTitleCase(s);
        }

        /// <summary>
		/// 将字符串数组转换为实际的数据数组。例如字符串格式[1,2,3,4,5]，可以转成实际的数组对象<br />
		/// Converts a string array into an actual data array. For example, the string format [1,2,3,4,5] can be converted into an actual array object
		/// </summary>
		/// <typeparam name="T">类型对象</typeparam>
		/// <param name="value">字符串数据</param>
		/// <param name="selector">转换方法</param>
		/// <returns>实际的数组</returns>
		public static T[] TiaoTiaoToStringArray<T>(this string value, Func<string, T> selector)
        {
            if (value.IndexOf('[') >= 0) value = value.Replace("[", "");
            if (value.IndexOf(']') >= 0) value = value.Replace("]", "");

            string[] splits = value.Split(new char[] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries);
            return splits.Select(selector).ToArray();
        }

        /// <summary>
		/// 将字符串数组转换为实际的数据数组。支持byte,sbyte,bool,short,ushort,int,uint,long,ulong,float,double，使用默认的十进制，例如字符串格式[1,2,3,4,5]，可以转成实际的数组对象<br />
		/// Converts a string array into an actual data array. Support byte, sbyte, bool, short, ushort, int, uint, long, ulong, float, double, use the default decimal, 
		/// such as the string format [1,2,3,4,5], which can be converted into an actual array Object
		/// </summary>
		/// <typeparam name="T">类型对象</typeparam>
		/// <param name="value">字符串数据</param>
		/// <returns>实际的数组</returns>
        public static T[] TiaoTiaoToStringArray<T>(this string value)
        {
            Type type = typeof(T);
            if (type == typeof(byte)) return (T[])(object)value.TiaoTiaoToStringArray(byte.Parse);
            else if (type == typeof(sbyte)) return (T[])(object)value.TiaoTiaoToStringArray(sbyte.Parse);
            else if (type == typeof(bool)) return (T[])(object)value.TiaoTiaoToStringArray(bool.Parse);
            else if (type == typeof(short)) return (T[])(object)value.TiaoTiaoToStringArray(short.Parse);
            else if (type == typeof(ushort)) return (T[])(object)value.TiaoTiaoToStringArray(ushort.Parse);
            else if (type == typeof(int)) return (T[])(object)value.TiaoTiaoToStringArray(int.Parse);
            else if (type == typeof(uint)) return (T[])(object)value.TiaoTiaoToStringArray(uint.Parse);
            else if (type == typeof(long)) return (T[])(object)value.TiaoTiaoToStringArray(long.Parse);
            else if (type == typeof(ulong)) return (T[])(object)value.TiaoTiaoToStringArray(ulong.Parse);
            else if (type == typeof(float)) return (T[])(object)value.TiaoTiaoToStringArray(float.Parse);
            else if (type == typeof(double)) return (T[])(object)value.TiaoTiaoToStringArray(double.Parse);
            else if (type == typeof(DateTime)) return (T[])(object)value.TiaoTiaoToStringArray(DateTime.Parse);
#if !NET20 && !NET35
            else if (type == typeof(Guid)) return (T[])(object)value.TiaoTiaoToStringArray(Guid.Parse);
#endif
            else if (type == typeof(string)) return (T[])(object)value.TiaoTiaoToStringArray(m => m);
            else throw new Exception("use ToArray<T>(Func<string,T>) method instead");
        }

        /// <summary>
        /// 十六进制字符串转字节数组
        /// </summary>
        /// <param name="hex"></param>
        /// <returns></returns>
        public static byte[] TiaoTiaoToHexBytes(this string hex)
        {
            MemoryStream ms = new MemoryStream();

            for (int i = 0; i < hex.Length; i++)
            {
                if ((i + 1) < hex.Length)
                {
                    if (GetHexCharIndex(hex[i]) >= 0 && GetHexCharIndex(hex[i + 1]) >= 0)
                    {
                        // 这是一个合格的字节数据
                        ms.WriteByte((byte)(GetHexCharIndex(hex[i]) * 16 + GetHexCharIndex(hex[i + 1])));
                        i++;
                    }
                }
            }

            byte[] result = ms.ToArray();
            ms.Dispose();
            return result;
        }

        #region .Net 版本号处理

        /// <summary>
        /// .net 文件版本号拆分
        /// </summary>
        /// <param name="version">版本号</param>
        /// <returns></returns>
        public static DotNetVersionDto TiaoTiaoParseDotNetVersion(this string version)
        {
            if (TiaoTiaoIsNullOrEmpty(version)) return default!;

            var dto = new DotNetVersionDto
            {
                OrgVersion = version
            };

            var arr = version.Split('.').ToList();

            if (arr.Count < 4)
            {
                for (var i = 0; i < 4 - arr.Count; i++)
                {
                    arr.Add("0");
                }
            }

            dto.Major = !int.TryParse(arr[0], out var major) ? 0 : major;
            dto.Version = !int.TryParse(arr[1], out var v) ? 0 : v;
            dto.MinorNumber = !int.TryParse(arr[2], out var mn) ? 0 : mn;
            dto.BuildNumber = !int.TryParse(arr[3], out var bn) ? 0 : bn;

            if (arr.Count <= 4) return dto;

            var otherArr = new string[arr.Count - 4];
            Array.Copy(arr.ToArray(), otherArr, otherArr.Length);
            dto.CustomVersion = arr.TiaoTiaoJoin(".");

            return dto;
        }

        /// <summary>
        /// 判断两个.net 版本号是否相同
        /// </summary>
        /// <param name="versionA">第一个版本号</param>
        /// <param name="versionB">第二个版本号</param>
        /// <returns></returns>
        public static bool TiaoTiaoEqualsDotNetVersion(this string versionA, string versionB)
        {
            var dtoA = TiaoTiaoParseDotNetVersion(versionA);
            var dtoB = TiaoTiaoParseDotNetVersion(versionB);

            if (dtoA == null || dtoB == null) return false;

            // 主要版本有一个不同 就代码版本号不同
            if (dtoA.Major != dtoB.Major || dtoB.Version != dtoB.Version
                || dtoA.MinorNumber != dtoB.MinorNumber || dtoA.BuildNumber != dtoB.BuildNumber) return false;

            // 上面的主要版本 都相同
            // 扩展版本都为空，则为版本号相同
            if (dtoA.CustomVersion.TiaoTiaoIsNullOrEmpty() && dtoB.CustomVersion.TiaoTiaoIsNullOrEmpty()) return true;

            // 扩展版本都不空，
            if (dtoA.CustomVersion.TiaoTiaoIsNotNullOrEmpty() && dtoB.CustomVersion.TiaoTiaoIsNotNullOrEmpty())
            {
                // 内容不同，则版本不同
                if (dtoA.CustomVersion != dtoB.CustomVersion) return false;

                // 内容相同，则版本相同
                return true;
            }

            return false;
        }

        /// <summary>
        /// .Net 版本号比对 
        /// </summary>
        /// <param name="versionA">第一个版本号字符串</param>
        /// <param name="major">第二个版本号主版本号</param>
        /// <param name="version">第二个版本号子版本号</param>
        /// <param name="minorNumber">第二个版本号修订号</param>
        /// <param name="buildNumber">第二个版本号编译号</param>
        /// <param name="customVersion">第二个版本号自定义号</param>
        /// <returns></returns>
        public static bool TiaoTiaoEqualsDotNetVersion(this string versionA, int major, int version, int minorNumber, int buildNumber, string? customVersion)
        {
            return versionA.TiaoTiaoEqualsDotNetVersion($"{major}.{version}.{minorNumber}.{buildNumber}{(customVersion.TiaoTiaoIsNullOrEmpty() ? string.Empty : $".{customVersion}")}");
        }

        #endregion

        #region

        static int GetHexCharIndex(char ch)
        {
            switch (ch)
            {
                case '0': return 0;
                case '1': return 1;
                case '2': return 2;
                case '3': return 3;
                case '4': return 4;
                case '5': return 5;
                case '6': return 6;
                case '7': return 7;
                case '8': return 8;
                case '9': return 9;
                case 'A':
                case 'a': return 10;
                case 'B':
                case 'b': return 11;
                case 'C':
                case 'c': return 12;
                case 'D':
                case 'd': return 13;
                case 'E':
                case 'e': return 14;
                case 'F':
                case 'f': return 15;
                default: return -1;
            }
        }

        #endregion
    }
}
